#pragma once

// includes
#include <boost/random.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>

typedef boost::mt19937 grng_engine;     // the random number engine used, grng = global random number generator

namespace grng      // avoid namespace pollution
{
    // code for getting and setting static generator adapted from Kevin Martin's suggestion on the Boost mailing list, July 26th, 2009
    void grng_setEngine(boost::shared_ptr<grng_engine> ptr);
    grng_engine& grng_getEngine();
}

// declaration
template<class Dist>
class CRandom : public boost::variate_generator<grng_engine&,Dist> // notice the reference, we do not want to copy the engine
{
public:
    /** use for Dist:
    CRandomI Ci(min,max);   uses uniform_int<int>       - integral types
    CRandomR Cr(min,max);   uses uniform_real<double>   - floating point types

    for other ditributions or types, use:
    CRandom<distribution<optional type>> Cr(0--3 params, depending on distribution);
    for distributions and params, see: http://www.boost.org/doc/libs/1_39_0/libs/random/random-distributions.html
    */

    // forwarding constructors
    explicit CRandom()
        : variate_generator<grng_engine&,Dist>(
        grng::grng_getEngine(),
        Dist()
        )
    { } 
    template<class P>
    explicit CRandom(const P & p1)
        : variate_generator<grng_engine&,Dist>(
        grng::grng_getEngine(),
        Dist(p1)
        )
    { } 
    template<class P1, class P2>
    explicit CRandom(const P1 & p1, const P2 & p2)
        : variate_generator<grng_engine&,Dist>(
        grng::grng_getEngine(),
        Dist(p1, p2)
        )
    { } 
    template<class P1, class P2, class P3>
    explicit CRandom(const P1 & p1, const P2 & p2, const P3 & p3)
        : variate_generator<grng_engine&,Dist>(
        grng::grng_getEngine(),
        Dist(p1, p2, p3)
        )
    { }
};
// end declaration


// shorthand typedefs
// random integer within range
typedef CRandom<boost::uniform_int<int>>                CRandomI;  // even int is default type   , specify it in case it ever changes
// random real number within range
typedef CRandom<boost::uniform_real<double>>            CRandomR;  // even double is default type, specify it in case it ever changes
// random boolean
typedef CRandom<boost::bernoulli_distribution<double>>  CRandomB;  // even double is default type, specify it in case it ever changes
// random boolean
typedef CRandom<boost::uniform_01<double>>              CRandom01; // even double is default type, specify it in case it ever changes


// useful functions for generating random numbers
namespace grng { static CRandom01 _C01; }   // make a global instance of CRandom01 for use with RandLim & RandIdx, avoid namespace pollution

// floating point and integer types need different treatments
template <class T>
typename boost::enable_if<boost::is_floating_point<T>, T>::type  // floating point types
RandLim(const T& tLower_, const T& tUpper_)
{
    return T(tLower_ + grng::_C01()*(tUpper_-tLower_));
}

template <class T>
typename boost::enable_if<boost::is_integral<T>, T>::type        // integral types
RandLim(const T& tLower_, const T& tUpper_)
{
    return T(tLower_ + grng::_C01()*((tUpper_+1)-tLower_));
}

template <class T>
typename boost::enable_if<boost::is_integral<T>, T>::type        // integral types
RandIdx(const T& tSize_)
{
    return T(grng::_C01()*tSize_); // this will always return between 0 and size-1 as the random number will never be exactly 1
}


// much shorter but much more to type
//#include <boost/function.hpp>
//// makes a boost::random variate_generator and wraps it in a boost::function
//template<class Distribution_>
//boost::function<typename Distribution_::result_type ()> grng_make_vg(const Distribution_& d)
//{
//    boost::variate_generator<grng_engine&, Distribution_> vg(grng_getEngine(), d);
//    return boost::function<typename Distribution_::result_type ()>(vg);
//
//    /** make variate_generator with user provided distribution
//    wrapped in boost::function, can also be used with std:generate
//
//    usage examples:
//    boost::function<int ()>     Ci = rng_make_vg(boost::uniform_int<int>              (1,100));
//    boost::function<double ()>  Cr = rng_make_vg(boost::uniform_real<double>          (4.5,98.8));
//    boost::function<bool ()>    Cb = rng_make_vg(boost::bernoulli_distribution<double>(.5));
//    for distributions and params, see: http://www.boost.org/doc/libs/1_39_0/libs/random/random-distributions.html
//    */
//}